﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Text;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Model;

namespace UtilZ.Dotnet.Ex.CommandLineParser
{
    /// <summary>
    /// 命令解析器
    /// </summary>
    public class CommandParser
    {
        private static CommandParser _default = null;
        /// <summary>
        /// 获取默认命令解析器
        /// </summary>
        public static CommandParser Default
        {
            get
            {
                if (_default == null)
                {
                    _default = new CommandParser();
                }
                return _default;
            }
        }


        private readonly List<CommandLineInfo> _commandLineInfos = new List<CommandLineInfo>();
        /// <summary>
        /// 
        /// </summary>
        public CommandParser()
        {

        }


        /// <summary>
        /// 注册指定命令类型
        /// </summary>
        /// <typeparam name="T">指定命令类型</typeparam>
        public void RegistCommand<T>() where T : class
        {
            string err;
            if (!this.PrimitiveRegistCommand(typeof(T), out err))
            {
                throw new ArgumentException($"类型{typeof(T).FullName}添加失败.{err}");
            }
        }

        /// <summary>
        /// 注册指定命令类型
        /// </summary>
        /// <param name="types">指定命令类型集合</param>
        public void RegistCommand(IEnumerable<Type> types)
        {
            if (types == null)
            {
                throw new ArgumentNullException(nameof(types));
            }

            string err;
            foreach (var type in types)
            {
                this.PrimitiveRegistCommand(type, out err);
            }
        }

        /// <summary>
        /// 注册指定命令类型
        /// </summary>
        /// <param name="type">指定命令类型</param>
        public void RegistCommand(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException(nameof(type));
            }

            string err;
            if (!this.PrimitiveRegistCommand(type, out err))
            {
                throw new ArgumentException($"类型{type.FullName}不是有效的命令类型.{err}");
            }
        }

        private bool PrimitiveRegistCommand(Type type, out string err)
        {
            if (this._commandLineInfos.Exists(t => { return t.CommandType == type; }))
            {
                err = $"类型{type.FullName}已注册，不允许重复注册.";
                return false;
            }

            if (!type.GetConstructors().Any(t => { return t.GetParameters().Length == 0; }))
            {
                err = $"类型{type.FullName}没有无参构造函数.";
                return false;
            }

            Attribute att = type.GetCustomAttribute(typeof(CommandAttribute), true);
            if (att == null)
            {
                err = $"类型{type.FullName}未标特性{typeof(CommandAttribute).FullName}";
                return false;
            }
            CommandAttribute commandAtt = (CommandAttribute)att;

            if (this._commandLineInfos.Any(t =>
            {
                return string.Equals(t.CommandAtt.Name, commandAtt.Name, StringComparison.OrdinalIgnoreCase) ||
                string.Equals(t.CommandAtt.Name, commandAtt.FullName, StringComparison.OrdinalIgnoreCase) ||
                string.Equals(t.CommandAtt.FullName, commandAtt.FullName, StringComparison.OrdinalIgnoreCase) ||
                 string.Equals(t.CommandAtt.Name, commandAtt.FullName, StringComparison.OrdinalIgnoreCase);
            }))
            {
                err = $"类型{type.FullName}标记特性{typeof(CommandAttribute).FullName}中的\"命令简称\"或\"命令全称\"重复.";
                return false;
            }

            foreach (CommandLineInfo commandLineInfo in this._commandLineInfos)
            {
                if (string.Equals(commandLineInfo.CommandAtt.Name, commandAtt.Name, StringComparison.OrdinalIgnoreCase))
                {
                    err = $"类型{type.FullName}标记特性{typeof(CommandAttribute).FullName}中的\"命令简称\"与{commandLineInfo.CommandType.FullName}中的\"命令简称\"重复.";
                    return false;
                }

                if (string.Equals(commandLineInfo.CommandAtt.Name, commandAtt.FullName, StringComparison.OrdinalIgnoreCase))
                {
                    err = $"类型{type.FullName}标记特性{typeof(CommandAttribute).FullName}中的\"命令简称\"与{commandLineInfo.CommandType.FullName}中的\"命令全称\"重复.";
                    return false;
                }

                if (string.Equals(commandLineInfo.CommandAtt.FullName, commandAtt.FullName, StringComparison.OrdinalIgnoreCase))
                {
                    err = $"类型{type.FullName}标记特性{typeof(CommandAttribute).FullName}中的\"命令全称\"与{commandLineInfo.CommandType.FullName}中的\"命令全称\"重复.";
                    return false;
                }

                if (string.Equals(commandLineInfo.CommandAtt.FullName, commandAtt.Name, StringComparison.OrdinalIgnoreCase))
                {
                    err = $"类型{type.FullName}标记特性{typeof(CommandAttribute).FullName}中的\"命令全称\"与{commandLineInfo.CommandType.FullName}中的\"命令简称\"重复.";
                    return false;
                }
            }



            var noRequiredOptions = new List<CommandLineOptionInfo>();
            var requiredOptions = new List<CommandLineOptionInfo>();
            CommandLineOptionAttribute optionAtt;
            var proInfos = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

            IValueRange valueRange;
            List<string> addOptionsNames = new List<string>();
            List<string> addOptionsFullNames = new List<string>();

            foreach (var proInfo in proInfos)
            {
                att = proInfo.GetCustomAttribute(typeof(CommandLineOptionAttribute), true);
                if (att == null)
                {
                    continue;
                }

                valueRange = TypeValueRange.CreatePropertyValueRange(proInfo);

                optionAtt = (CommandLineOptionAttribute)att;
                if (addOptionsNames.Contains(optionAtt.Name))
                {
                    throw new ArgumentException($"属性{type.FullName}.{proInfo.Name}上的命令行选项简称在该类中已存在.");
                }

                if (addOptionsFullNames.Contains(optionAtt.FullName))
                {
                    throw new ArgumentException($"属性{type.FullName}.{proInfo.Name}上的命令行选项全称在该类中已存在.");
                }

                if (proInfo.GetCustomAttribute(typeof(RequiredAttribute), true) != null)
                {
                    requiredOptions.Add(new CommandLineOptionInfo(proInfo, optionAtt, valueRange));
                }
                else
                {
                    noRequiredOptions.Add(new CommandLineOptionInfo(proInfo, optionAtt, valueRange));
                }

                addOptionsNames.Add(optionAtt.Name);
                addOptionsFullNames.Add(optionAtt.FullName);
            }

            this._commandLineInfos.Add(new CommandLineInfo(type, commandAtt, requiredOptions, noRequiredOptions));
            err = null;
            return true;
        }

        /// <summary>
        /// 移除指定的命令类型
        /// </summary>
        /// <typeparam name="T">指定的命令类型</typeparam>
        public void Remove<T>() where T : class
        {
            this.Remove(typeof(T));
        }

        /// <summary>
        /// 移除指定的命令类型
        /// </summary>
        /// <param name="type">指定的命令类型</param>
        /// <exception cref="ArgumentNullException">指定的命令类型为null异常</exception>
        public void Remove(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException(nameof(type));
            }

            this._commandLineInfos.RemoveAll(t => t.CommandType == type);
        }

        /// <summary>
        /// 清空已注册的命令
        /// </summary>
        public void Clear()
        {
            this._commandLineInfos.Clear();
        }


        /// <summary>
        /// 解析命令行,解析成功返回命令对象，解析失败返回null.
        /// </summary>
        /// <param name="args">命令参数数组</param>
        /// <param name="result">解析结果实例</param>
        /// <param name="err">错误信息</param>
        /// <returns>解析成功返回命令对象，解析失败返回null</returns>
        public bool TryParse(string[] args, out object result, out string err)
        {
            if (args == null || args.Length == 0)
            {
                result = null;
                err = $"缺少必须参数";
                return false;
            }

            return this.PrimitiveTryParse(args, out result, out err);
        }

        /// <summary>
        /// 解析命令行,解析成功返回命令对象，解析失败返回null.
        /// </summary>
        /// <param name="commandLineText">命令行文本</param>
        /// <param name="result">解析结果实例</param>
        /// <param name="err">错误信息</param>
        /// <returns>解析成功返回命令对象，解析失败返回null</returns>
        public bool TryParse(string commandLineText, out object result, out string err)
        {
            if (string.IsNullOrEmpty(commandLineText))
            {
                result = null;
                err = $"缺少必须参数";
                return false;
            }

            List<string> strList = SplitCommandLine(commandLineText.Trim());
            return this.PrimitiveTryParse(strList, out result, out err);
        }


        private bool PrimitiveTryParse(IList<string> args, out object result, out string err)
        {
            result = null;
            err = null;

            string commandName = args[0];
            CommandLineInfo commandLineInfo = this._commandLineInfos.Find(t =>
            {
                return string.Equals(t.CommandAtt.Name, commandName, StringComparison.OrdinalIgnoreCase) ||
                string.Equals(t.CommandAtt.FullName, commandName, StringComparison.OrdinalIgnoreCase);
            });
            if (commandLineInfo == null)
            {
                err = $"无效的命令\"{commandName}\"";
                return false;
            }

            if (args.Count <= 1 &&
                commandLineInfo.RequiredOptions.Count != 0)
            {
                err = $"无效的命令\"{commandName}({commandLineInfo.CommandAtt.Des})\",缺少必须参数,必选参数列表：\r\n{string.Join("\r\n", commandLineInfo.RequiredOptions)}.";
                return false;
            }

            object obj = Activator.CreateInstance(commandLineInfo.CommandType);
            CommandLineOptionInfo optionInfo;
            object value;
            int requiredOptionCount = 0;

            for (int i = 1; i < args.Count; i += 2)
            {
                optionInfo = commandLineInfo.NoRequiredOptions.FirstOrDefault((t) =>
                {
                    return string.Equals(args[i], t.CommandOptionAtt.Name, StringComparison.OrdinalIgnoreCase) ||
                    string.Equals(args[i], t.CommandOptionAtt.FullName, StringComparison.OrdinalIgnoreCase);
                });
                if (optionInfo == null)
                {
                    optionInfo = commandLineInfo.RequiredOptions.FirstOrDefault((t) =>
                    {
                        return string.Equals(args[i], t.CommandOptionAtt.Name, StringComparison.OrdinalIgnoreCase) ||
                        string.Equals(args[i], t.CommandOptionAtt.FullName, StringComparison.OrdinalIgnoreCase);
                    });

                    if (optionInfo != null)
                    {
                        requiredOptionCount++;
                    }
                    else
                    {
                        err = $"无效的选项参数\"{args[i]}\".";
                        return false;
                    }
                }

                if (optionInfo.Converter != null)
                {
                    if (!optionInfo.Converter.TryConvertTo(optionInfo.ProInfo, args[i + 1], out value))
                    {
                        err = $"选项参数\"{args[i]}\"值\"{args[i + 1]}\"无效.";
                        return false;
                    }
                }
                else
                {
                    if (!ConvertEx.TryConvertTo(optionInfo.ProInfo.PropertyType, args[i + 1], out value))
                    {
                        err = $"选项参数\"{args[i]}\"值\"{args[i + 1]}\"无效.不是有效的\"{optionInfo.ProInfo.PropertyType.FullName}\"类型.";
                        return false;
                    }
                }

                if (optionInfo.ValueRange != null)
                {
                    if (!optionInfo.ValueRange.TryValidate(value, obj, optionInfo.ProInfo))
                    {
                        err = $"选项{args[i]}值\"{args[i + 1]}\"无效，{args[i]}值有效范围：{optionInfo.ValueRange.GetValueRangeDes(value, obj, optionInfo.ProInfo)},输入值:{optionInfo.CommandOptionAtt.HelpText}";
                        return false;
                    }

                }

                optionInfo.ProInfo.SetValue(obj, value, null);
            }

            if (requiredOptionCount < commandLineInfo.RequiredOptions.Count)
            {
                err = $"缺少必选参数,必选参数列表：\r\n{string.Join("\r\n", commandLineInfo.RequiredOptions)}.";
                return false;
            }

            result = obj;
            return true;
        }

        private static List<string> SplitCommandLine(string commandOptionsText)
        {
            var list = new List<string>();
            int beginIndex = 0;
            bool inQuotes = false, hasEscChar = false;
            string option;

            for (int i = 0; i < commandOptionsText.Length; i++)
            {
                if (commandOptionsText[i] == '\"')
                {
                    if (i > 0 && commandOptionsText[i - 1] == '\\')
                    {
                        //如果是转义的英文双引号，则忽略。
                        hasEscChar = true;
                        continue;
                    }

                    if (inQuotes)
                    {
                        option = commandOptionsText.Substring(beginIndex + 1, i - beginIndex - 1).Trim();
                        if (hasEscChar)
                        {
                            option = option.Replace("\\\"", "\"");
                            hasEscChar = false;
                        }

                        if (!string.IsNullOrWhiteSpace(option))
                        {
                            list.Add(option);
                        }
                        beginIndex = i + 1;
                        inQuotes = false;
                    }
                    else
                    {
                        beginIndex = i;
                        inQuotes = true;
                        continue;
                    }
                }
                else if (commandOptionsText[i] == ' ' && !inQuotes)
                {
                    if (beginIndex < 0)
                    {
                        beginIndex = i;
                        continue;
                    }
                    else
                    {
                        option = commandOptionsText.Substring(beginIndex, i - beginIndex).Trim();
                        if (hasEscChar)
                        {
                            option = option.Replace("\\\"", "\"");
                            hasEscChar = false;
                        }

                        if (!string.IsNullOrWhiteSpace(option))
                        {
                            list.Add(option);
                        }
                        beginIndex = i + 1;
                    }
                }

            }

            if (beginIndex < commandOptionsText.Length)
            {
                option = commandOptionsText.Substring(beginIndex).Trim();
                if (!string.IsNullOrWhiteSpace(option))
                {
                    list.Add(option);
                }
            }

            return list;
        }


        /// <summary>
        /// 获取所有命令帮助文本
        /// </summary>
        /// <param name="splitLine">命令间分隔线</param>
        /// <returns>所有命令帮助文本</returns>
        public string GetAllCommandHelpText(string splitLine = "------------------------------------------")
        {
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < this._commandLineInfos.Count; i++)
            {
                CommandLineInfo commandLineInfo = this._commandLineInfos[i];
                sb.Append(commandLineInfo.CommandAtt.Des);
                sb.Append('(');
                sb.Append(commandLineInfo.CommandAtt.Name);
                sb.Append('|');
                sb.Append(commandLineInfo.CommandAtt.FullName);
                sb.Append(')');
                sb.Append(" 必选参数");
                sb.Append(commandLineInfo.RequiredOptions.Count);
                sb.Append("个");
                sb.Append(",可选参数");
                sb.Append(commandLineInfo.NoRequiredOptions.Count);
                sb.Append("个.");
                sb.AppendLine();
                sb.AppendLine();

                sb.AppendLine("必选参数列表:");
                foreach (CommandLineOptionInfo optionInfo in commandLineInfo.RequiredOptions)
                {
                    sb.Append(optionInfo.CommandOptionAtt.Des);
                    sb.Append('(');
                    sb.Append(optionInfo.CommandOptionAtt.Name);
                    sb.Append('|');
                    sb.Append(optionInfo.CommandOptionAtt.FullName);
                    sb.Append(')');
                    sb.Append('[');
                    sb.Append(optionInfo.CommandOptionAtt.HelpText);
                    sb.Append(']');
                    sb.AppendLine();
                }

                sb.AppendLine();
                sb.AppendLine("可选参数列表:");
                foreach (CommandLineOptionInfo optionInfo in commandLineInfo.NoRequiredOptions)
                {
                    sb.Append(optionInfo.CommandOptionAtt.Des);
                    sb.Append('(');
                    sb.Append(optionInfo.CommandOptionAtt.Name);
                    sb.Append('|');
                    sb.Append(optionInfo.CommandOptionAtt.FullName);
                    sb.Append(')');
                    sb.Append('[');
                    sb.Append(optionInfo.CommandOptionAtt.HelpText);
                    sb.Append(']');
                    sb.AppendLine();
                }

                if (i + 1 < this._commandLineInfos.Count)
                {
                    sb.AppendLine(splitLine);
                }
            }

            return sb.ToString();
        }

    }

    internal class CommandLineInfo
    {
        public readonly TypeValueRange Range;
        public readonly Type CommandType;
        public readonly CommandAttribute CommandAtt;

#if NET4_0
        public readonly ICollection<CommandLineOptionInfo> RequiredOptions;
        public readonly ICollection<CommandLineOptionInfo> NoRequiredOptions;
#else
        public readonly IReadOnlyCollection<CommandLineOptionInfo> RequiredOptions;
        public readonly IReadOnlyCollection<CommandLineOptionInfo> NoRequiredOptions;
#endif

        public CommandLineInfo(Type commandType, CommandAttribute commandAtt,
            List<CommandLineOptionInfo> requiredOptions, List<CommandLineOptionInfo> noRequiredOptions)
        {
            this.Range = new TypeValueRange(commandType);
            this.CommandType = commandType;
            this.CommandAtt = commandAtt;
            this.RequiredOptions = requiredOptions.OrderBy(t => { return t.CommandOptionAtt.Order; }).ToArray();
            this.NoRequiredOptions = noRequiredOptions.OrderBy(t => { return t.CommandOptionAtt.Order; }).ToArray();
        }
    }

    internal class CommandLineOptionInfo
    {
        public readonly PropertyInfo ProInfo;
        public readonly CommandLineOptionAttribute CommandOptionAtt;
        public readonly ICommandLineOptionConverter Converter = null;
        public readonly IValueRange ValueRange;

        public CommandLineOptionInfo(PropertyInfo proInfo, CommandLineOptionAttribute commandOptionAtt, IValueRange valueRange)
        {
            this.ProInfo = proInfo;
            this.CommandOptionAtt = commandOptionAtt;

            var converterType = commandOptionAtt.ConverterType;
            if (converterType != null)
            {
                if (converterType.GetInterface(typeof(ICommandLineOptionConverter).FullName) == null)
                {
                    throw new ArgumentException($"属性\"{proInfo.DeclaringType.FullName}.{proInfo.Name}\"上定义的转换器类型未实现\"{typeof(ICommandLineOptionConverter).FullName}\"接口");
                }

                if (!converterType.GetConstructors().Any(t => { return t.GetParameters().Length == 0; }))
                {
                    throw new ArgumentException($"转换器类型\"{converterType.FullName}\"缺少无参构造函数");
                }

                this.Converter = (ICommandLineOptionConverter)Activator.CreateInstance(converterType);
            }

            this.ValueRange = valueRange;
        }

        /// <summary>
        /// 重写ToString
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return $"{CommandOptionAtt.Name}({CommandOptionAtt.FullName})[{CommandOptionAtt.Des}({CommandOptionAtt.HelpText})]";
        }
    }
}
